package com.gitee.wsl.ext

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.flow.toCollection
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.jvm.JvmName

fun<T> Result.Companion.Ok(value:T) = Result.success(value)

fun<T> Result.Companion.Error(throwable:Throwable) = Result.failure<T>(throwable)


inline fun <T, U> Result<T>.flatMap(f: (T) -> Result<U>) = fold({ f(it) }, { Result.failure<Nothing>(it) })


/**
 * If a [Result] is a failure, maps the underlying [Throwable] to a new [Throwable].
 */
inline fun <N : Throwable, T> Result<T>.mapFailure(
    f: (exception: Throwable) -> N
): Result<T> {
    return when (val exception = exceptionOrNull()) {
        null -> Result.success(getOrThrow())
        else -> Result.failure(f(exception))
    }
}

/**
 * Calls the specified function block and returns its encapsulated result if invocation was successful, catching any
 * non-fatal exceptions thrown from the block function execution and encapsulating them as failures.
 */
@JvmName("tryCatch")
inline fun <R> Result.Companion.catch(f: () -> R): Result<R> =
    arrow.core.raise.catch({ success(f()) }) { failure(it) }

/**
 * Retrieves the success of a Result, or throws the failure. Alias of `getOrThrow`, included for consistency with ErrorOr.
 */
fun <A> Result<A>.orThrow() = getOrThrow()


/**
 * Map success to Unit, included for consistency with Either.
 */
fun <T> Result<T>.unit() = map { }

/**
 * Performs an effect over successes but maps the original value back into
 * the Result.
 */
inline fun <A, B> Result<A>.tap(f: (A) -> B): Result<A> = this.map { a ->
    f(a)
    a
}

/**
 * Performs an effect over successes but maps the original value back into
 * the Result.  This is useful for mixing with validation functions.
 */
inline fun <A> Result<A>.flatTap(f: (A) -> Result<Any>): Result<A> = this.flatMap { a ->
    f(a).map { a }
}

/**
 * Returns false if Success or returns the result of the given predicate to the Failure value.
 */
inline fun <T> Result<T>.isFailure(predicate: (Throwable) -> Boolean): Boolean =
    fold(onFailure = predicate, onSuccess = { false })

/**
 * Returns false if Failure or returns the result of the given predicate to the Success value.
 */
inline fun <T> Result<T>.isSuccess(predicate: (T) -> Boolean): Boolean =
    fold(onFailure = { false }, onSuccess = predicate)

/**
 * Recovers errors with a function from Throwable to Result.
 */
inline fun <T> Result<T>.handleFailureWith(f: (Throwable) -> Result<T>): Result<T> = recoverCatching {
    f(it).getOrThrow()
}


/**
 * Compose a [transform] operation on the success value [A] into [B] whilst flattening [Result].
 * @see mapCatching if you want run a function that catches and maps with `(A) -> B`
 */
/* @OptIn(ExperimentalContracts::class)
 inline fun <A, B> Result<A>.flatMap(transform: (value: A) -> Result<B>): Result<B> {
    contract { callsInPlace(transform, InvocationKind.AT_MOST_ONCE) }
    return map(transform).fold(::identity, ::failure)
}*/

 fun <T> List<Result<T>>.sequence(): Result<List<T>> {
    val ss = this.map { result -> result.getOrElse { return Result.failure(it) } }
    return Result.success(ss)
}

fun <T: Comparable<T>> List<Result<T>>.notNullValues() = mapNotNull { result -> result.getOrNull() }

fun <T: Comparable<T>> List<Result<T>>.minOrNull() = notNullValues().minOrNull()

fun <T: Comparable<T>> List<Result<T>>.manOrNull() = notNullValues().maxOrNull()

class InnerException(message: String = "inner value error"): Exception(message)

/**
 * Turns a nullable value into a [Result].
 */
inline fun <A : Throwable, B> B?.toResult(error: () -> A): Result<B> =
    this?.let { Result.success(it) } ?: Result.failure(error())


@OptIn(ExperimentalContracts::class)
inline fun <V, E> Result<V>.andThen(transform: (V) -> Result<E>): Result<E> {
    contract {
        callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
    }
    if (isSuccess) {
        val value = getOrNull() ?: return Result.failure(InnerException())
        return transform(value)
    } else {
        val exception = exceptionOrNull() ?: return Result.failure(InnerException())
        return Result.failure(exception)
    }
}

@OptIn(ExperimentalContracts::class)
inline fun <V, E> Result<V>.dispatch(transform: (V) -> E): Result<E> {
    contract {
        callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
    }

    if (isSuccess) {
        val value = getOrNull() ?: return Result.failure(InnerException())
        return kotlin.runCatching {
            transform(value)
        }
    } else {
        val exception = exceptionOrNull() ?: return Result.failure(InnerException())
        return Result.failure(exception)
    }
}

// 由于 async 需要一个 CoroutineScope 来启动，这里创建一个Result的CoroutineScope
 interface CoroutineResultScope<V> : CoroutineScope {
     suspend fun <V> Result<V>.bind(): V
}

/*internal class CoroutineResultScopeImpl<V> (
    delegate: CoroutineScope,
) : CoroutineResultScope<V>, CoroutineScope by delegate {

    private val mutex = Mutex()
    var result: Result<V>? = null

    // 使用 bind 支持协程的结构化编程，这样当一个协程任务异常失败时，
    // 取消其他的协程任务
    override suspend fun <V> Result<V>.bind(): V {
        return if (isSuccess) {
            getOrNull() ?: throw InnerException()
        } else {
            mutex.withLock {
                if (result == null) {
                    result =  Result.failure(this.exceptionOrNull() ?: InnerException())
                    coroutineContext.cancel()
                }
                throw CancellationException()
            }
        }
    }
}

@OptIn(ExperimentalContracts::class)
 suspend inline fun <V, E> Result<V>.coroutineDispatch(crossinline block: suspend CoroutineResultScope<E>.(V) -> E): Result<E> {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    if (isSuccess) {
        lateinit var receiver: CoroutineBindingScopeImpl<E>
        return try {
            coroutineScope {
                receiver = CoroutineBindingScopeImpl(this)
                val value = getOrNull() ?: throw InnerException()
                with(receiver) {
                    Result.success(block(value))
                }
            }
        } catch (ex: CancellationException) {
            receiver.result!!
        }
    } else {
        return Result.failure(exceptionOrNull() ?: InnerException())
    }
}*/


/**
 * Executes a suspend function [action] if the current result is a success.
 *
 * @param action A suspend function to be executed if the result is a success.
 * @return The current Result<T> instance.
 */
@OptIn(ExperimentalContracts::class)
 suspend inline fun <T> Result<T>.onSuccessSuspend(crossinline action: suspend (value: T) -> Unit): Result<T> {
    contract {
        callsInPlace(action, InvocationKind.AT_MOST_ONCE)
    }
    if (isSuccess) action(getOrThrow())
    return this
}

/**
 * Executes a suspend function [action] if the current result is a failure.
 *
 * @param action A suspend function to be executed if the result is a failure.
 * @return The current Result<T> instance.
 */
@OptIn(ExperimentalContracts::class)
suspend inline fun <T> Result<T>.onFailureSuspend(crossinline action: suspend (exception: Throwable) -> Unit): Result<T> {
    contract {
        callsInPlace(action, InvocationKind.AT_MOST_ONCE)
    }
    exceptionOrNull()?.let { action(it) }
    return this
}


/**
 * Performs a fold operation on the Result, executing [onSuccess] if the Result is a success,
 * or [onFailure] if the Result is a failure.
 *
 * @param onSuccess A suspend function to be executed on success.
 * @param onFailure A suspend function to be executed on failure.
 * @return Result of applying the corresponding function based on the current Result state.
 */
@OptIn(ExperimentalContracts::class)
suspend inline fun <R, T> Result<T>.foldSuspend(
    crossinline onSuccess: suspend (value: T) -> R,
    crossinline onFailure: suspend (exception: Throwable) -> R,
): R {
    contract {
        callsInPlace(onSuccess, InvocationKind.AT_MOST_ONCE)
        callsInPlace(onFailure, InvocationKind.AT_MOST_ONCE)
    }
    return when (val exception = exceptionOrNull()) {
        null -> onSuccess(getOrThrow())
        else -> onFailure(exception)
    }
}

/**
 * Performs a mapping operation on the Result, transforming the value if the Result is a success.
 *
 * @param transform A suspend function to transform the value on success.
 * @return Result<R> representing the transformed value or failure based on the current Result state.
 */
@OptIn(ExperimentalContracts::class)
suspend inline fun <R, T> Result<T>.mapSuspend(crossinline transform: suspend (value: T) -> R): Result<R> {
    contract {
        callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
    }
    return when {
        isSuccess -> Result.success(transform(getOrThrow()))
        else -> Result.failure(exceptionOrNull()!!)
    }
}

/**
 * Recovers from a failure by executing [transform] to create a new value if the Result is a failure.
 *
 * @param transform A suspend function to transform the exception on failure.
 * @return Result<R> representing the recovered value or original success based on the current Result state.
 */
@OptIn(ExperimentalContracts::class)
suspend inline fun <R, T : R> Result<T>.recoverSuspend(crossinline transform: suspend (exception: Throwable) -> R): Result<R> {
    contract {
        callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
    }
    return when (val exception = exceptionOrNull()) {
        null -> this
        else -> Result.success(transform(exception))
    }
}

inline fun <T> Result<T>.replaceIfFailure(onException: (Throwable) -> T) = if (isSuccess) { this } else { runCatching { onException(exceptionOrNull()!!) } }

fun Result<*>.exceptionOrThrow(): Throwable =
    exceptionOrNull() ?: throw IllegalStateException("Expected an exception, but result was successful.")

 fun <T> Result<Result<T>>.flatten(): Result<T> = getOrElse { return Result.failure(it) }

 fun <T> Iterable<Result<T>>.filterSuccess(): List<T> = filter { it.isSuccess }.map { it.getOrThrow() }
 fun Iterable<Result<*>>.filterFailure(): List<Throwable> = mapNotNull { it.exceptionOrNull() }

 fun <T> Iterable<Result<T>>.onEachFailure(block: (Throwable) -> Unit): Iterable<Result<T>> =
    onEach { it.exceptionOrNull()?.let(block) }

 fun <T> Iterable<Result<T>>.toResultList(): Result<List<T>> = toResultCollection(ArrayList())
 fun <T> Iterable<Result<T>>.toResultSet(): Result<Set<T>> = toResultCollection(LinkedHashSet())
 fun <T, C : MutableCollection<in T>> Iterable<Result<T>>.toResultCollection(destination: C): Result<C> {
    var error: Throwable? = null
    val result = takeWhile {
        if (it.isFailure) error = it.exceptionOrThrow()
        !it.isFailure
    }.map { it.getOrThrow() }.toCollection(destination)
    return error?.let { Result.failure(it) } ?: Result.success(result)
}

 fun Flow<Result<*>>.filterFailure(): Flow<Throwable> = filter { it.isFailure }.map { it.exceptionOrThrow() }
 fun <T> Flow<Result<T>>.filterSuccess(): Flow<T> = filter { it.isSuccess }.map { it.getOrThrow() }
 fun <T> Flow<Result<T>>.onEachFailure(block: (Throwable) -> Unit): Flow<Result<T>> =
    onEach { if (it.isFailure) block(it.exceptionOrThrow()) }

 suspend fun <T> Flow<Result<T>>.toResultList(destination: MutableList<T> = ArrayList()): Result<List<T>> =
    toResultCollection(destination)

 suspend fun <T> Flow<Result<T>>.toResultSet(destination: MutableSet<T> = LinkedHashSet()): Result<Set<T>> =
    toResultCollection(destination)

 suspend fun <T, C : MutableCollection<in T>> Flow<Result<T>>.toResultCollection(destination: C): Result<C> {
    var error: Throwable? = null
    val result = takeWhile {
        if (it.isFailure) error = it.exceptionOrThrow()
        !it.isFailure
    }.map { it.getOrThrow() }.toCollection(destination)
    return error?.let { Result.failure(it) } ?: Result.success(result)
}

